#include "jogo.h"
#include "music.h"

static Jogo jogo;
int INTERVALO_ENTRE_NOVAS_BOLAS;
int OBJECTIVOS_POR_BOLA;

int segundos;
int timer_som_colisao; // Timer que permitirá controlar o som das colisões em "background"
int timer_nova_bola; // Timer que indicará quando adicionar uma nova bola "inimiga" ao jogo

// Timers e dados para o controlo dos bónus
int timer_parar_jogador;
int timer_parar_bolas;
int timer_alterar_velocidade; // Para os bónus BONUS_ACELERAR e BONUS_ABRANDAR simultâneamente
int timer_invencivel;
int timer_missil;
static Bonus_velocidade bonus_velocidade;

void drawMenu()
{
	setCursor(3, 32);
	printString("*** COLISION ***\n");
	
	setCursor(6, 34);
	printString("Modo de Jogo\n");
	draw_rect_modo(WHITE | BRIGHT, video_mem);
	setCursor(8, 27);
	printString("Temporizado     Objectivos\n");
	draw_rect_temporizado_focus(WHITE | BRIGHT, video_mem); // Rectângulo na opção "Temporizado" (rectângulo a indicar o foco)
	switch(jogo.modo)
	{
		case TEMPORIZADO:
			draw_rect_temporizado_selected(GREEN | RED | BRIGHT, video_mem); // Rectângulo na opção "Temporizado" (rectângulo a indicar a selecção)
			break;
		case OBJECTIVOS:
			draw_rect_objectivos_selected(GREEN | RED | BRIGHT, video_mem); // Rectângulo na opção "Objectivos"
			break;
	}	
	
	setCursor(12, 34);
	printString("Dificuldade\n");
	draw_rect_dificuldade(WHITE, video_mem);
	setCursor(14, 27);
	printString("Facil     Medio    Dificil\n");
	switch(jogo.dificuldade)
	{
		case FACIL:
			draw_rect_facil_selected(GREEN | RED | BRIGHT, video_mem);
			break;
		case MEDIO:
			draw_rect_medio_selected(GREEN | RED | BRIGHT, video_mem);
			break;
		case DIFICIL:
			draw_rect_dificil_selected(GREEN | RED | BRIGHT, video_mem);
			break;
	}
	
	setCursor(18, 37);
	printString("Bonus\n");
	draw_rect_bonus(WHITE, video_mem);
	setCursor(20, 27);
	printString("Activados     Desactivados\n");
	switch(jogo.bonus_activados)
	{
		case ACTIVADOS:
			draw_rect_activados_selected(GREEN | RED | BRIGHT, video_mem);
			break;
		case DESACTIVADOS:
			draw_rect_desactivados_selected(GREEN | RED | BRIGHT, video_mem);
			break;
	}
	
	draw_rect_jogar_sair(WHITE, video_mem);
	setCursor(26, 31);
	printString("JOGAR        SAIR\n");
	draw_rect_jogar(GREEN, video_mem);
	draw_rect_sair(RED, video_mem);
}

void menu()
{
	jogo.modo = TEMPORIZADO;
	jogo.dificuldade = MEDIO;
	jogo.bonus_activados = ACTIVADOS;

	typedef enum{O_TEMPORIZADO, O_OBJECTIVOS, O_FACIL, O_MEDIO, O_DIFICIL, O_ACTIVADOS, O_DESACTIVADOS, O_JOGAR, O_SAIR} Opcao;
	Opcao opcao = O_TEMPORIZADO;
	Bool sair = false;
	uchar c = 0;
	clear_screen(BLACK, video_mem);	

	drawMenu();
	
	do{
		while(queueEmpty(&queue_keyboard)) ; 
		c = queueGet(&queue_keyboard);
		
		switch(c)
		{
			case LEFT_KEY:
				play_sound(1000, 20);
				switch(opcao)
				{
					case O_OBJECTIVOS:
						opcao = O_TEMPORIZADO;
						draw_rect_temporizado_focus(WHITE | BRIGHT, video_mem);
						draw_rect_objectivos_focus(BLACK, video_mem);
						break;
						
					case O_MEDIO:
						opcao = O_FACIL;
						draw_rect_facil_focus(WHITE | BRIGHT, video_mem);
						draw_rect_medio_focus(BLACK, video_mem);
						break;
					case O_DIFICIL:
						opcao = O_MEDIO;
						draw_rect_medio_focus(WHITE | BRIGHT, video_mem);
						draw_rect_dificil_focus(BLACK, video_mem);
						break;
						
					case O_DESACTIVADOS:
						opcao = O_ACTIVADOS;
						draw_rect_activados_focus(WHITE | BRIGHT, video_mem);
						draw_rect_desactivados_focus(BLACK, video_mem);
						break;

					case O_SAIR:
						opcao = O_JOGAR;
						draw_rect_jogar_focus(WHITE | BRIGHT, video_mem);
						draw_rect_sair_focus(BLACK, video_mem);
						break;
					
					default: break;
				}
				break;
				
			case RIGHT_KEY:
				play_sound(1000, 20);
				switch(opcao)
				{
					case O_TEMPORIZADO:
						opcao = O_OBJECTIVOS;
						draw_rect_temporizado_focus(BLACK, video_mem);
						draw_rect_objectivos_focus(WHITE | BRIGHT, video_mem);
						break;
					
					case O_FACIL:
						opcao = O_MEDIO;
						draw_rect_medio_focus(WHITE | BRIGHT, video_mem);
						draw_rect_facil_focus(BLACK, video_mem);
						break;
					case O_MEDIO:
						opcao = O_DIFICIL;
						draw_rect_dificil_focus(WHITE | BRIGHT, video_mem);
						draw_rect_medio_focus(BLACK, video_mem);
						break;
						
					case O_ACTIVADOS:
						opcao = O_DESACTIVADOS;
						draw_rect_desactivados_focus(WHITE | BRIGHT, video_mem);
						draw_rect_activados_focus(BLACK, video_mem);
						break;
						
					case O_JOGAR:
						opcao = O_SAIR;
						draw_rect_sair_focus(WHITE | BRIGHT, video_mem);
						draw_rect_jogar_focus(BLACK, video_mem);
						break;
					
					default: break;
				}
				break;
				
			case UP_KEY:
				play_sound(1000, 20);
				switch(opcao)
				{
					case O_FACIL:
					case O_MEDIO:
					case O_DIFICIL:
						opcao = O_TEMPORIZADO;
						draw_rect_facil_focus(BLACK, video_mem);
						draw_rect_medio_focus(BLACK, video_mem);
						draw_rect_dificil_focus(BLACK, video_mem);
						draw_rect_temporizado_focus(WHITE | BRIGHT, video_mem);
						draw_rect_modo(WHITE | BRIGHT, video_mem);
						draw_rect_dificuldade(WHITE, video_mem);
						break;
						
					case O_ACTIVADOS:
					case O_DESACTIVADOS:
						opcao = O_MEDIO;
						draw_rect_activados_focus(BLACK, video_mem);
						draw_rect_desactivados_focus(BLACK, video_mem);
						draw_rect_medio_focus(WHITE | BRIGHT, video_mem);
						draw_rect_dificuldade(WHITE | BRIGHT, video_mem);
						draw_rect_bonus(WHITE, video_mem);
						break;
						
					case O_JOGAR:
					case O_SAIR:
						opcao = O_ACTIVADOS;
						draw_rect_jogar_focus(BLACK, video_mem);
						draw_rect_sair_focus(BLACK, video_mem);
						draw_rect_activados_focus(WHITE | BRIGHT, video_mem);
						draw_rect_bonus(WHITE | BRIGHT, video_mem);
						draw_rect_jogar_sair(WHITE, video_mem);
						break;
					
					default: break;
				}
				break;
				
			case DOWN_KEY:
				play_sound(1000, 20);
				switch(opcao)
				{
					case O_TEMPORIZADO:
					case O_OBJECTIVOS:
						opcao = O_MEDIO;
						draw_rect_temporizado_focus(BLACK, video_mem);
						draw_rect_objectivos_focus(BLACK, video_mem);
						draw_rect_medio_focus(WHITE | BRIGHT, video_mem);
						draw_rect_dificuldade(WHITE | BRIGHT, video_mem);
						draw_rect_modo(WHITE, video_mem);
						break;
						
					case O_FACIL:
					case O_MEDIO:
					case O_DIFICIL:
						opcao = O_ACTIVADOS;
						draw_rect_facil_focus(BLACK, video_mem);
						draw_rect_medio_focus(BLACK, video_mem);
						draw_rect_dificil_focus(BLACK, video_mem);
						draw_rect_activados_focus(WHITE | BRIGHT, video_mem);
						draw_rect_bonus(WHITE | BRIGHT, video_mem);
						draw_rect_dificuldade(WHITE, video_mem);
						break;
					
					case O_ACTIVADOS:
					case O_DESACTIVADOS:
						opcao = O_JOGAR;
						draw_rect_activados_focus(BLACK, video_mem);
						draw_rect_desactivados_focus(BLACK, video_mem);
						draw_rect_jogar_focus(WHITE | BRIGHT, video_mem);
						draw_rect_jogar_sair(WHITE | BRIGHT, video_mem);
						draw_rect_bonus(WHITE, video_mem);
						break;
					
					default: break;
				}
				break;	
			
			case ENTER_KEY:
				play_sound(800, 20);
				switch(opcao)
				{
					case O_TEMPORIZADO:
						jogo.modo = TEMPORIZADO;
						draw_rect_temporizado_selected(RED | GREEN | BRIGHT, video_mem);
						draw_rect_objectivos_selected(BLACK, video_mem);
						break;
					case O_OBJECTIVOS:
						jogo.modo = OBJECTIVOS;
						draw_rect_temporizado_selected(BLACK, video_mem);
						draw_rect_objectivos_selected(RED | GREEN | BRIGHT, video_mem);
						break;
						
					case O_FACIL:
						jogo.dificuldade = FACIL;
						draw_rect_facil_selected(RED | GREEN | BRIGHT, video_mem);
						draw_rect_medio_selected(BLACK, video_mem);
						draw_rect_dificil_selected(BLACK, video_mem);
						break;
					case O_MEDIO:
						jogo.dificuldade = MEDIO;
						draw_rect_facil_selected(BLACK, video_mem);
						draw_rect_medio_selected(RED | GREEN | BRIGHT, video_mem);
						draw_rect_dificil_selected(BLACK, video_mem);
						break;
					case O_DIFICIL:
						jogo.dificuldade = DIFICIL;
						draw_rect_facil_selected(BLACK, video_mem);
						draw_rect_medio_selected(BLACK, video_mem);
						draw_rect_dificil_selected(RED | GREEN | BRIGHT, video_mem);
						break;
						
					case O_ACTIVADOS:
						jogo.bonus_activados = ACTIVADOS;
						draw_rect_activados_selected(RED | GREEN | BRIGHT, video_mem);
						draw_rect_desactivados_selected(BLACK, video_mem);
						break;
					case O_DESACTIVADOS:
						jogo.bonus_activados = DESACTIVADOS;
						draw_rect_desactivados_selected(RED | GREEN | BRIGHT, video_mem);
						draw_rect_activados_selected(BLACK, video_mem);
						break;
						
						
					case O_JOGAR:
						start();
						clear_screen(BLACK, video_mem);
						opcao = O_TEMPORIZADO;
						drawMenu();
						break;
						
					case O_SAIR:
						sair = true;
						break;
					default: break;
				}
				break;
		}
	}while(!sair);
}

void start()
{
	switch(jogo.dificuldade)
	{
		case FACIL:
			INTERVALO_ENTRE_NOVAS_BOLAS = 30000;
			VELOCIDADE_INICIAL_BOLA = 3;
			VELOCIDADE_MISSIL = 2;
			OBJECTIVOS_POR_BOLA = 4;
			break;
			
		case MEDIO:
			INTERVALO_ENTRE_NOVAS_BOLAS = 25000;
			VELOCIDADE_INICIAL_BOLA = 5;
			VELOCIDADE_MISSIL = 3;
			OBJECTIVOS_POR_BOLA = 3;
			break;
			
		case DIFICIL:
		default:
			INTERVALO_ENTRE_NOVAS_BOLAS = 20000;
			VELOCIDADE_INICIAL_BOLA = 7;
			VELOCIDADE_MISSIL = 4;
			OBJECTIVOS_POR_BOLA = 2;
			break;
	}

	// Inicializar as informações da bola do jogador
	jogo.jogador.raio = RAIO_BOLA;
	jogo.jogador.x = HRES/2;
	jogo.jogador.y = VRES/2;
	jogo.jogador.vx = jogo.jogador.vy = 0;
	jogo.jogador.tipo = BOLA_JOGADOR;
	jogo.jogador.cor = COR_BOLA_JOGADOR;	
	
	// Inicializar a estrutura de jogo
	jogo.nbolas = jogo.nbonus = 0;
	novo_step = false;
	segundos_actualizados = false;
	jogo.bolas = NULL;
	jogo.bonus = NULL;
	jogo.missil = NULL;
	jogo.objectivo = NULL;
	jogo.nobjectivos = 0;
	mouse_dx = 0;
	mouse_dy = 0;

	if(jogo.modo == TEMPORIZADO)
	{
		int i = 0;
		for(; i < NUMERO_INICIAL_BOLAS; i++)
			adicionarBolaInimiga();
	}
	
	timer_som_colisao = 0;
	timer_parar_jogador = 0;
	timer_parar_bolas = 0;
	timer_alterar_velocidade = 0;
	timer_invencivel = 0;
	timer_missil = 0;
	bonus_velocidade = VNORMAL;
	if(jogo.modo == TEMPORIZADO) setTimer(&timer_nova_bola, INTERVALO_ENTRE_NOVAS_BOLAS);
	segundos = 0;
	
	play();
}

void play()
{
	Bool jogar = true;
	Bool up = false, down = false, left = false, right = false; // indicam se o jogador tem as respectivas teclas de direcção premidas
	clear_screen(BLACK, image_buffer);
	
	draw_line(0, TOP_BORDER - 1, HRES - 1, TOP_BORDER - 1, WHITE | BRIGHT, video_mem);
	draw_line(0, TOP_BORDER - 2, HRES - 1, TOP_BORDER - 2, WHITE, video_mem);

	if(jogo.modo == TEMPORIZADO)
	{
		setCursor(1, COL_TEMPO);
		printString("Tempo: 0\n");
	}
	else
	{
		setCursor(1, COL_TEMPO);
		printString("Objectivos: 0\n");
	}
	setCursor(1, COL_TITULO);
	printString("*** COLISION ***\n");	
	setCursor(1, COL_NBOLAS);
	printString("N. Bolas: ");	
	{
		char aux_str[10];
		if(jogo.modo == TEMPORIZADO)
			itoa(NUMERO_INICIAL_BOLAS, aux_str, 10);
		else
			itoa(0, aux_str, 10);
		printString(aux_str);
	}
	
	if(jogo.modo == OBJECTIVOS)
	{
		jogo.objectivo = newBola(0, 0, 0, 0, OBJECTIVO, RAIO_OBJECTIVO, COR_OBJECTIVO);
		objectivoMoveRandom();
	}
	
	while(jogar)
	{
	
		// Tratar o teclado
		while(!queueEmpty(&queue_keyboard)) {
			uchar c = queueGet(&queue_keyboard);
			switch(c)
			{
				case ESCAPE_KEY: jogar = false; break;
				case DOWN_KEY: down = true; break;
				case DOWN_RELEASED: down = false; break;
				case UP_KEY: up = true; break;
				case UP_RELEASED: up = false; break;
				case LEFT_KEY: left = true; break;
				case LEFT_RELEASED: left = false; break;
				case RIGHT_KEY: right = true; break;
				case RIGHT_RELEASED: right = false; break;				
				default: break;
			}
		}
		
		if(getTimerReached(&timer_som_colisao))
			speaker_off();
			
		if(jogo.modo == TEMPORIZADO && getTimerReached(&timer_nova_bola))
		{
			adicionarBolaInimiga();
			setTimer(&timer_nova_bola, INTERVALO_ENTRE_NOVAS_BOLAS);
		}
		
		if(getTimerReached(&timer_parar_jogador))
		{
			jogo.jogador.cor = (timer_invencivel == 0) ? COR_BOLA_JOGADOR : WHITE;
		}
			
		getTimerReached(&timer_parar_bolas); // Não é feito nada aqui
		
		if(getTimerReached(&timer_alterar_velocidade))
			bonus_velocidade = VNORMAL;

		if(getTimerReached(&timer_invencivel))
			jogo.jogador.cor = COR_BOLA_JOGADOR;
			
		if(getTimerReached(&timer_missil) && jogo.missil != NULL)
		{
			free(jogo.missil);
			jogo.missil = NULL;
		}
			
		if(segundos_actualizados)
		{
			updateGameTime();
			segundos_actualizados = false;
		}
		
		if(novo_step && jogar)
		{
			novo_step = false;
			clear_game_screen(BLACK, image_buffer);
			
			if(timer_parar_jogador == 0)
			{
				disable();
				// Tratar o rato
				
				/*if(abs(mouse_dy) > 120)
				{
					mouse_dy = 0;
					contador = 1;
				}
				else if(abs(mouse_dx) > 120)
				{
					//mouse_dy = mouse_dx;
					mouse_dx = 0;
					contador = 0;
				}*/
				if(abs(mouse_dx) > 80 || abs(mouse_dy) > 80)
				{
					mouse_dx = 0;
					mouse_dy = 0;
					contador = 0;
				}
				jogo.jogador.x += mouse_dx;
				jogo.jogador.y += mouse_dy;
				mouse_dx = 0;
				mouse_dy = 0;
				enable();
				
				// Mover a bola do jogador
				if(up || down || left || right) 
				{
					if(up) jogo.jogador.y -= VELOCIDADE_JOGADOR_TECLADO;
					if(down) jogo.jogador.y += VELOCIDADE_JOGADOR_TECLADO;
					if(left) jogo.jogador.x -= VELOCIDADE_JOGADOR_TECLADO;
					if(right) jogo.jogador.x += VELOCIDADE_JOGADOR_TECLADO;
				}
				if(jogo.jogador.x - jogo.jogador.raio < 0) jogo.jogador.x = jogo.jogador.raio;
				if(jogo.jogador.x + jogo.jogador.raio >= HRES) jogo.jogador.x = HRES - jogo.jogador.raio - 1;
				if(jogo.jogador.y - jogo.jogador.raio < TOP_BORDER) jogo.jogador.y = jogo.jogador.raio + TOP_BORDER;
				if(jogo.jogador.y + jogo.jogador.raio >= VRES) jogo.jogador.y = VRES - jogo.jogador.raio - 1;			
			}
			
			moveInternalObjects(); // Mover as outras bolas
			if(jogo.missil != NULL) drawBola(jogo.missil, image_buffer); // Desenhar o míssil, se existente
			if(jogo.modo == OBJECTIVOS && jogo.objectivo != NULL) drawBola(jogo.objectivo, image_buffer);
			drawBola(&jogo.jogador, image_buffer);
			swap_game_buffers(image_buffer, video_mem);
			
			if(timer_invencivel == 0 && check_game_over())
			{
				setCursor(14, 35);
				printString("GAME  OVER\n");
				jogar = false;
				int f;
				for(f = 500; f >= 100; f -= 10)
					play_sound(f, 40);
				
				setCursor(16, 25);
				#ifndef EMBED
					printString("Introduza o seu nome: ");
					fflush(stdout);
					saveClassificationToFile();
				#else
					printString("Prima Enter ou Escape para sair\n");
					uchar c;
					while(queueEmpty(&queue_keyboard) || ((c = queueGet(&queue_keyboard)) != ENTER_KEY && c != ESCAPE_KEY)) ;
				#endif
			}
			
			checkBonusCollision();
			if(jogo.modo == OBJECTIVOS) checkObjectivoCollision();
			moverMissil();
		}
		
	}
	
	speaker_off();
	freeJogo();
}

void moveInternalObjects()
{
	int i;
	
	// Redesenhar todos os bónus
	for(i = 0; i < jogo.nbonus; i++)
	{
		drawBonus(jogo.bonus[i], image_buffer);
	}
	
	// Apagar todas as bolas
	//for(i = 0; i < jogo.nbolas; i++)
		//eraseBola(jogo.bolas[i], image_buffer);
	
	// Deslocar e desenhar todas as bolas
	for(i = 0; i < jogo.nbolas; i++)
	{
		if(timer_parar_bolas == 0) updatePositionBola(jogo.bolas[i], bonus_velocidade);
		drawBola(jogo.bolas[i], image_buffer);
	}

	checkBallCollision();
}

void checkBallCollision() {
	int i;
	int j;
	Bola* b1 = NULL;
	Bola* b2 = NULL;
	for(i = 0; i < jogo.nbolas; i++) {
		b1 = jogo.bolas[i];
		for(j = i+1; j < jogo.nbolas; j++) {
			b2 = jogo.bolas[j];
			if (i == j) continue; // Caso de analisar a mesma bola
			if(distance(b1->x, b1->y, b2->x, b2->y) > (b1->raio + b2->raio) ) continue;
			SolveBallCollision(b1, b2);	

			// Certificar-se de que as bolas não saem do ecrã
			if(b1->x - b1->raio < 0) b1->x = b1->raio;
			if(b1->x + b1->raio >= HRES) b1->x = HRES - b1->raio - 1;
			if(b1->y - b1->raio < TOP_BORDER) b1->y = b1->raio + TOP_BORDER;
			if(b1->y + b1->raio >= VRES) b1->y = VRES - b1->raio - 1;
			
			if(b2->x - b2->raio < 0) b2->x = b2->raio;
			if(b2->x + b2->raio >= HRES) b2->x = HRES - b2->raio - 1;
			if(b2->y - b2->raio < TOP_BORDER) b2->y = b2->raio + TOP_BORDER;
			if(b2->y + b2->raio >= VRES) b2->y = VRES - b2->raio - 1;			
		}
	}
}

void SolveBallCollision(Bola * b1, Bola * b2) {
	Vector2d* position1 = newVector2d(b1->x, b1->y);
	Vector2d* position2 = newVector2d(b2->x, b2->y);
	Vector2d* delta = subtractVector2d(position1, position2);
	
	float d = distance(b1->x, b1->y, b2->x, b2->y);
	Vector2d* mtd = newVector2d(0,0);
	if ( d != 0 ) {
		mtd = multiplyVector2d(delta, (((b1->raio + b2->raio)-d)/d));
	} else {
		d = b1->raio + b2->raio - 1.0;
		delta = newVector2d(b1->raio + b2->raio, 0);

		mtd = multiplyVector2d(delta,((b1->raio + b2->raio)-d)/d);
	}
	
	// resolve intersection
	float im1 = 1 / 1; // inverse mass quantities
	float im2 = 1 / 1;
	
	// push-pull them apart
	position1 = addVector2d(position1, multiplyVector2d(mtd, im1 / (im1 + im2)));
	position2 = subtractVector2d(position2, multiplyVector2d(mtd, im2 / (im1 + im2)));
	b1->x = position1->x; b1->y = position1->y;
	b2->x = position2->x; b2->y = position2->y;
	
	// impact speed
	Vector2d* v = newVector2d(b1->vx - b2->vx, b1->vy - b2->vy);
	normalizeVector2d(mtd);
	float vn = dotVector2d(v, mtd);
	
	// sphere intersecting but moving away from each other already
	if (vn > 0) return;
		
	// collision impulse
	float restituicao = 1;
	float i = (-(1.0 + restituicao) * vn) / (im1 + im2);
	Vector2d* impulse = multiplyVector2d(mtd, i);
		
	// change in momentum
	Vector2d* vel1 = newVector2d(b1->vx, b1->vy);
	Vector2d* vel2 = newVector2d(b2->vx, b2->vy);
	vel1 = addVector2d(vel1, multiplyVector2d(impulse, im1));
	vel2 = subtractVector2d(vel2, multiplyVector2d(impulse, im2));
	b1->vx = vel1->x; b1->vy = vel1->y;
	b2->vx = vel2->x; b2->vy = vel2->y;
}

Bool check_game_over()
{
	int i = 0;
	for(; i < jogo.nbolas; i++)
		if(distance(jogo.bolas[i]->x, jogo.bolas[i]->y, jogo.jogador.x, jogo.jogador.y) <= (jogo.jogador.raio + jogo.bolas[i]->raio) || (
		jogo.missil != NULL && distance(jogo.missil->x, jogo.missil->y, jogo.jogador.x, jogo.jogador.y) <= (jogo.jogador.raio + jogo.missil->raio)))
			return true;
	return false;
}

void checkBonusCollision()
{
	int i = 0;
	for(; i < jogo.nbonus; i++)
	{
		if(distance(jogo.bonus[i]->x, jogo.bonus[i]->y, jogo.jogador.x, jogo.jogador.y) < (jogo.jogador.raio + jogo.bonus[i]->raio))
		{
			if(jogo.bonus[i]->tipo & BONUS_BOM)
				start_sound(1000);
			else
				start_sound(150);
			setTimer(&timer_som_colisao, 200);
			
			switch(jogo.bonus[i]->tipo)
			{
				case BONUS_PARAR_BOLAS:
					bonus_parar_bolas();
					break;
				case BONUS_PARAR_JOGADOR:
					bonus_parar_jogador();
					break;
				case BONUS_ABRANDAR: 
					bonus_abrandar();
					break;
				case BONUS_ACELERAR:
					bonus_acelerar();
					break;
				case BONUS_INVENCIVEL:
					bonus_invencivel();
					break;
				case BONUS_MISSIL:
					bonus_missil();
					break;
			}
			
			removeBonus(i);
		}
	}
}

int distance(int x1, int y1, int x2, int y2)
{
	int dx = x2 - x1;
	int dy = y2 - y1;
	return sqrt(dx*dx + dy*dy);
}

void addBola(Bola* bola)
{
	jogo.bolas = (Bola**)realloc(jogo.bolas, ++jogo.nbolas * sizeof(Bola*));
	jogo.bolas[jogo.nbolas-1] = bola;
}

void addBonus(Bonus* bonus)
{
	jogo.bonus = (Bonus**)realloc(jogo.bonus, ++jogo.nbonus * sizeof(Bonus*));
	jogo.bonus[jogo.nbonus-1] = bonus;
}

void removeBonus(int index)
{
	if(index < jogo.nbonus)
	{
		free(jogo.bonus[index]);
		jogo.bonus[index] = NULL;
		
		int i;
		for(i = index; i < (jogo.nbonus - 1); i++)
			jogo.bonus[i] = jogo.bonus[i + 1];
			
		jogo.nbonus--;
		jogo.bonus = (Bonus**)realloc(jogo.bonus, jogo.nbonus * sizeof(Bonus*));
	}
}

void freeJogo()
{
	int i;
	// Limpar o array de bolas
	for(i = 0; i < jogo.nbolas; i++)
		free(jogo.bolas[i]);
	free(jogo.bolas);
	jogo.bolas = NULL;
	jogo.nbolas = 0;
	for(i = 0; i < jogo.nbonus; i++)
		free(jogo.bonus[i]);
	free(jogo.bonus);
	jogo.bonus = NULL;
	jogo.nbonus = 0;
	if(jogo.missil != NULL)
	{
		free(jogo.missil);
		jogo.missil = NULL;
	}
	if(jogo.objectivo != NULL)
	{
		free(jogo.objectivo);
		jogo.objectivo = NULL;
		jogo.nobjectivos = 0;
	}
}

void adicionarBolaInimiga()
{
	float x;
	float y;
	float direction;
	float vx;
	float vy;
	
	do{
		x = rand() % (HRES - 2*RAIO_BOLA - 1) + RAIO_BOLA;
		y = rand() % (VRES - 2*RAIO_BOLA - TOP_BORDER - 1) + RAIO_BOLA + TOP_BORDER;
	}while(distance(x, y, jogo.jogador.x, jogo.jogador.y) < 300); // Certificar-se que a bola é criada a uma distância segura do jogador

	const float pi = acos(-1);
	
	direction = (float)(rand() % (int)(pi * 2000.0));
	vx = ((float)VELOCIDADE_INICIAL_BOLA) * cos(direction);
	vy = ((float)VELOCIDADE_INICIAL_BOLA) * sin(direction);
	
	addBola(newBola(x, y, vx, vy, BOLA_INIMIGA, RAIO_BOLA, COR_BOLA_INIMIGA));

	setCursor(1, COL_NBOLAS + 10);
	{
		char aux_str[10];
		itoa(jogo.nbolas, aux_str, 10);
		printString(aux_str);
	}
}

void adicionarBonus()
{
	int x;
	int y;
	char tipo;
	do{
		x = rand() % (HRES - 2*RAIO_BONUS - 1) + RAIO_BONUS;
		y = rand() % (VRES - 2*RAIO_BONUS - TOP_BORDER - 1) + RAIO_BONUS + TOP_BORDER;
	}while(distance(x, y, jogo.jogador.x, jogo.jogador.y) < 100); // Certificar-se que o bónus é criado a uma distância mínima do jogador

	int type = rand() % NUM_BONUS;

	switch(type)
	{
		case 0: tipo = BONUS_PARAR_BOLAS; break;
		case 1: tipo = BONUS_PARAR_JOGADOR; break;
		case 2: tipo = BONUS_ABRANDAR; break;
		case 3: tipo = BONUS_ACELERAR; break;
		case 4: tipo = BONUS_INVENCIVEL; break;
		case 5: tipo = BONUS_MISSIL; break;
		default: tipo = BONUS_ABRANDAR;
	}
	
	addBonus(newBonus(x, y, RAIO_BONUS, tipo, COR_BONUS));
}

void moverMissil()
{
	if(jogo.missil != NULL)
	{
		float dx = jogo.jogador.x - jogo.missil->x;
		float dy = jogo.jogador.y - jogo.missil->y;
		float dist = sqrt(dx*dx + dy*dy);
		
		dx = (dx / dist) * (float)VELOCIDADE_MISSIL;
		dy = (dy / dist) * (float)VELOCIDADE_MISSIL;
		
		jogo.missil->x += dx;
		jogo.missil->y += dy;
		
		if(jogo.missil->x < jogo.missil->raio)
			jogo.missil->x = jogo.missil->raio;
		else if(jogo.missil->x + jogo.missil->raio >= HRES)
			jogo.missil->x = HRES - jogo.missil->raio - 1;
		if(jogo.missil->y < jogo.missil->raio)
			jogo.missil->y = jogo.missil->raio;
		else if(jogo.missil->y + jogo.missil->raio >= VRES)
			jogo.missil->y = VRES - jogo.missil->raio - 1;
	}
}

void bonus_parar_bolas()
{
	setTimer(&timer_parar_bolas, TEMPO_BONUS_PARAR_BOLAS);
}

void bonus_parar_jogador()
{
	setTimer(&timer_parar_jogador, TEMPO_BONUS_PARAR_JOGADOR);
	jogo.jogador.cor = BLUE;
}

void bonus_abrandar()
{
	setTimer(&timer_alterar_velocidade, TEMPO_BONUS_ABRANDAR);
	bonus_velocidade = VMETADE;
}

void bonus_acelerar()
{
	setTimer(&timer_alterar_velocidade, TEMPO_BONUS_ACELERAR);
	bonus_velocidade = VDOBRO;
}

void bonus_invencivel()
{
	setTimer(&timer_invencivel, TEMPO_BONUS_INVENCIVEL);
	jogo.jogador.cor = WHITE;
}

void bonus_missil()
{
	if(jogo.missil != NULL)
		free(jogo.missil);

	setTimer(&timer_missil, TEMPO_BONUS_MISSIL);
	
	int x;
	int y;
	do{
		x = rand() % (HRES - 2*RAIO_MISSIL - 1) + RAIO_MISSIL;
		y = rand() % (VRES - 2*RAIO_MISSIL - TOP_BORDER - 1) + RAIO_MISSIL + TOP_BORDER;
	}while(distance(x, y, jogo.jogador.x, jogo.jogador.y) < 300); // Certificar-se que o míssil é criado a uma distância segura do jogador
	
	jogo.missil = newBola(x, y, 0, 0, MISSIL, RAIO_MISSIL, COR_MISSIL);
}


void objectivoMoveRandom()
{
	if(jogo.objectivo != NULL)
	{
		int x;
		int y;
		do{
			x = rand() % (HRES - 2*RAIO_OBJECTIVO - 1) + RAIO_OBJECTIVO;
			y = rand() % (VRES - 2*RAIO_OBJECTIVO - TOP_BORDER - 1) + RAIO_OBJECTIVO + TOP_BORDER;
		}while(distance(x, y, jogo.jogador.x, jogo.jogador.y) < 300); // Certificar-se que o objectivo é criado a uma distância mínima do jogador
		
		jogo.objectivo->x = x;
		jogo.objectivo->y = y;
	}
}

void checkObjectivoCollision()
{
	if(jogo.objectivo != NULL && distance(jogo.jogador.x, jogo.jogador.y, jogo.objectivo->x, jogo.objectivo->y) <= (jogo.jogador.raio + jogo.objectivo->raio))
	{
		objectivoMoveRandom();
		jogo.nobjectivos++;
		
		if(jogo.nobjectivos % OBJECTIVOS_POR_BOLA == 0) adicionarBolaInimiga();
		
		setCursor(1, COL_OBJECTIVOS + 12);
		{
			char aux_str[10];
			itoa(jogo.nobjectivos, aux_str, 10);
			printString(aux_str);
		}		
		
		start_sound(1100);
		setTimer(&timer_som_colisao, 250);
	}
}


void updateGameTime()
{
	segundos++;
	if(jogo.modo == TEMPORIZADO)
	{
		setCursor(1, COL_TEMPO + 7);
		{
			char aux_str[10];
			itoa(segundos, aux_str, 10);
			printString(aux_str);
		}
	}
	
	if(jogo.bonus_activados == ACTIVADOS && (rand() % 100 + 1) < PROB_BONUS_POR_SEGUNDO)
		adicionarBonus();
}

void saveClassificationToFile() {
	#ifdef EMBED
		return;
	#else
	
	RTC_DATE data_actual;
	rtc_read_date(&data_actual);
	RTC_TIME tempo_actual;
	rtc_read_time(&tempo_actual);
	
	// Ler o nome do jogador
	while(!queueEmpty(&queue_keyboard)) queueGet(&queue_keyboard); // Esvaziar a fila do teclado
	endKeyBoard();
	char nome[51];
	fgets(nome, 51, stdin);
	Bool ha_nome = false;
	initKeyBoard(handler_keyboard);
	
	// Retirar o '\n' final
	int i = 0;
	for(; i < 51; i++)
		if(nome[i] == '\n')
		{
			nome[i] = '\0';
			break;
		}
		else if(nome[i] != ' ' && nome[i] != '\t')
			ha_nome = true;
	
	
	if(ha_nome) // Se não foi introduzido nada, ignorar
	{
		char* buff = (char*)malloc(sizeof(char)*256);
	
		char dificuldade[9];
		switch(jogo.dificuldade){
			case FACIL: strcpy(dificuldade, "FACIL]  "); break;
			case MEDIO: strcpy(dificuldade, "MEDIO]  "); break;
			case DIFICIL: strcpy(dificuldade, "DIFICIL]"); break;
		}
		
		sprintf(buff, "[%2d/%.2d/%4d - %.2d:%.2d:%.2d]", data_actual.day, data_actual.month, 2000+data_actual.year, tempo_actual.hour, tempo_actual.min, tempo_actual.sec);
		switch(jogo.modo) {
			case TEMPORIZADO:
				sprintf(buff, "%s[%s%s", buff, "TEMPO-", dificuldade);
				sprintf(buff, "%s Tempo: %3d seg, N.Bolas: %2d (%s)\n", buff, segundos, jogo.nbolas, nome);
				break;
			case OBJECTIVOS:
				sprintf(buff, "%s[%s%s", buff, "OBJEC-", dificuldade);
				sprintf(buff, "%s Objectivos: %2d, N.Bolas: %2d (%s)\n", buff, jogo.nobjectivos, jogo.nbolas, nome);
				break;
		}
		int fd = open(CLASS_FILE_NAME, O_WRONLY | O_APPEND | O_CREAT, 0777);
		write(fd, buff, strlen(buff));
		close(fd);
		free(buff);
	}
	#endif
}
